﻿namespace System.Xml.Serialization.Internal
{
    using System.Reflection;

    internal class XmlRuntimeProperty :
        IXmlRuntimeProperty,
        IRuntimePropertyReader,
        IRuntimePropertyWriter,
        IXmlWriter,
        IXmlReader
    {
        private object _object;
        private PropertyInfo _property;

        private Type _objectType;
        private PropertyInfo _objectProperty;

        private bool _canRead;
        private bool _canWrite;
        private XmlAbstractSerializer _serializer;
        private XmlRuntimeSerializer _runtimeSerializer;

        public string PropertyName { get { return _property.Name; } }
        public Type PropertyType { get { return _property.PropertyType; } }
        public IXmlRuntimeProperty<T> GetRuntimeProperty<T>()
        {
            if (_property.PropertyType == typeof(T))
            {
                return new XmlRuntimeProperty<T>(_object, _property);
            }
            return null;
        }

        public IRuntimePropertyReader Reader { get { return this; } }
        public IRuntimePropertyWriter Writer { get { return this; } }

        private MethodInfo _read;
        private MethodInfo _write;

        public XmlRuntimeProperty(object o, Type objectType, PropertyInfo property)
        {
            _object = o;
            _property = property;
            _objectType = objectType;
            _objectProperty = _objectType.GetProperty(_property.Name);
            if (_objectProperty.PropertyType == _property.PropertyType)
            {
                if (IsSerializable(_objectProperty))
                {
                    if (_objectProperty.GetGetMethod() != null)
                    {
                        _canRead = true;
                        _read = _property.GetGetMethod();
                    }
                    if (_objectProperty.GetSetMethod() != null)
                    {
                        _canWrite = true;
                        _write = _property.GetSetMethod();
                    }
                    XmlSerializerAttribute serializerAttribute = GetXmlSerializerAttribute(_objectProperty);
                    if (serializerAttribute.Serializer != null)
                    {
                        if (serializerAttribute.Serializer.RuntimeType == null)
                        {
                            serializerAttribute.Serializer.InitializeRuntimeType(_objectProperty.PropertyType);
                        }
                        if (serializerAttribute.Serializer.RuntimeType == _objectProperty.PropertyType)
                        {
                            _serializer = serializerAttribute.Serializer;
                        }
                    }
                    XmlRuntimeSerializerAttribute runtimeSerializerAttribute = GetRuntimeXmlSerializerAttribute(_objectProperty);
                    if (runtimeSerializerAttribute.RuntimeSerializer != null)
                    {
                        if (runtimeSerializerAttribute.RuntimeSerializer.RuntimeType == null)
                        {
                            runtimeSerializerAttribute.RuntimeSerializer.InitializeRuntimeType(_objectProperty.PropertyType);
                        }
                        if (runtimeSerializerAttribute.RuntimeSerializer.RuntimeType == _objectProperty.PropertyType)
                        {
                            _runtimeSerializer = runtimeSerializerAttribute.RuntimeSerializer;
                        }
                    }
                }
            }
        }

        private static XmlSerializerAttribute GetXmlSerializerAttribute(PropertyInfo property)
        {
            XmlSerializerAttribute[] attributes = (XmlSerializerAttribute[])property.GetCustomAttributes(typeof(XmlSerializerAttribute), true);
            if (attributes.Length > 0)
            {
                return attributes[0];
            }
            else
            {
                foreach (Attribute attribute in property.GetCustomAttributes(true))
                {
                    Type attributeType = attribute.GetType();
                    if (typeof(XmlSerializerAttribute).IsAssignableFrom(attributeType) && attributeType.IsSealed && attributeType.IsPublic && attributeType.IsClass)
                    {
                        ConstructorInfo attributeConstructor = attributeType.GetConstructor(Type.EmptyTypes);
                        return (XmlSerializerAttribute)attributeConstructor.Invoke(null);
                    }
                }
            }
            return new XmlSerializerAttribute(property.PropertyType);
        }

        private static XmlRuntimeSerializerAttribute GetRuntimeXmlSerializerAttribute(PropertyInfo property)
        {
            XmlRuntimeSerializerAttribute[] attributes = (XmlRuntimeSerializerAttribute[])property.GetCustomAttributes(typeof(XmlRuntimeSerializerAttribute), true);
            if (attributes.Length > 0)
            {
                return attributes[0];
            }
            else
            {
                foreach (Attribute attribute in property.GetCustomAttributes(true))
                {
                    Type attributeType = attribute.GetType();
                    if (typeof(XmlRuntimeSerializerAttribute).IsAssignableFrom(attributeType) && attributeType.IsSealed && attributeType.IsPublic && attributeType.IsClass)
                    {
                        ConstructorInfo attributeConstructor = attributeType.GetConstructor(Type.EmptyTypes);
                        return (XmlRuntimeSerializerAttribute)attributeConstructor.Invoke(null);
                    }
                }
            }
            return new XmlRuntimeSerializerAttribute(property.PropertyType);
        }

        private static bool IsSerializable(PropertyInfo property)
        {
            System.Xml.Serialization.XmlIgnoreAttribute[] attributes = (System.Xml.Serialization.XmlIgnoreAttribute[])property.GetCustomAttributes(typeof(System.Xml.Serialization.XmlIgnoreAttribute), true);
            return attributes.Length == 0;
        }

        public XmlRuntimeSerializer RuntimeSerializer { get { return _runtimeSerializer; } }

        public bool CanSerialize { get { return _serializer != null; } }
        public bool CanRuntimeSerialize { get { return _runtimeSerializer != null; } }

        public bool CanRead { get { return _canRead; } }
        public bool CanWrite { get { return _canWrite; } }

        public override string ToString()
        {
            return _property.Name;
        }

        string IRuntimePropertyReader.Read()
        {
            if (_serializer != null && _canRead)
            {
                return _serializer.Serialize(_read.Invoke(_object, null));
            }
            return null;
        }

        void IRuntimePropertyWriter.Write(string value)
        {
            if (_serializer != null && _canWrite)
            {
                _write.Invoke(_object, new object[] { _serializer.Deserialize(value) });
            }
        }

        object IRuntimePropertyReader.ReadObject()
        {
            return ReadObject();
        }

        void IRuntimePropertyWriter.WriteObject(object value)
        {
            WriteObject(value);
        }

        private object ReadObject()
        {
            if (_canRead)
            {
                return _read.Invoke(_object, null);
            }
            return null;
        }

        private void WriteObject(object value)
        {
            if (_canWrite)
            {
                _write.Invoke(_object, new object[] { value });
            }
        }

        public object Object
        {
            get
            {
                return ReadObject();
            }
            set
            {
                WriteObject(value);
            }
        }


        void IXmlWriter.Write(IXmlPropertyWriter node, string propertyName)
        {
            if (_runtimeSerializer != null && _canRead)
            {
                _runtimeSerializer.Serialize(_read.Invoke(_object, null), node, propertyName);
            }
        }

        void IXmlReader.Read(IXmlPropertyReader node, string propertyName)
        {
            if (_runtimeSerializer != null && _canWrite)
            {
                _write.Invoke(_object, new object[] { _runtimeSerializer.Deserialize(node, propertyName) });
            }
        }
    }
}
